/**
 * 
 * BUG: Only one level of sub-factors is allowed
 */
package edu.umd.clip.lm.factors;

import java.util.*;
import org.w3c.dom.*;
import edu.umd.clip.lm.util.*;

/**
 * @author Denis Filimonov <den@cs.umd.edu>
 *
 */
public class FactorDescription {
	protected final static String XML_ELEMENT_NAME = "factor-description";
	protected final static String XML_ATTR_ID_NAME = "id";
	protected final static String XML_ATTR_NAME_NAME = "name";
	protected final static String XML_ATTR_PARENT_ID_NAME = "parent-id";
	protected final static String XML_ATTR_IS_OVERT_NAME = "is-overt";
	protected final static String XML_WORD_TO_OVERT_FACTOR = "word-to-overt-factor";
	protected final static String XML_ATTR_BITS_NAME = "bits"; // vocab size in bits
	
	private String id;
	private String name;
	private FactorDescription parent = null;
	private Dictionary dictionary = null;
	private Vector<Dictionary> dictionaries = null;
	private boolean overt = true;
	private WordToOvertFactorInterface wordToOvertFactor = null;
	private byte bits = 0; 
	
	public FactorDescription(String id, String n, FactorDescription p) {
		this.id = id;
		name = n;
		parent = p;
	}
	
	public FactorDescription(String id, String n) {
		this(id, n, null);
	}
	
	public String getName() {
		return name;
	}
	
	public FactorDescription getParent() {
		return parent;
	}
	
	public Dictionary getDictionary() {
		if (parent == null) return dictionary;
		throw new Error("FactorDescription::getDictionary(parentValue) must be used.");
	}
	
	public Dictionary getDictionary(int parentValue) {
		if (parent == null) return dictionary;
		if (dictionaries.size() <= parentValue) return null;
		return dictionaries.get(parentValue);
	}
	
	public void setDictionary(Dictionary dict) {
		if (parent == null) 
			dictionary = dict;
		else
			throw new Error("FactorDescription::setDictionary(dict,parentValue) must be used.");
	}
	
	public void setDictionary(int parentValue, Dictionary dict) {
		if (parent == null) dictionary = dict;
		else {
			if (dictionaries.size() <= parentValue) {
				dictionaries.setSize(parentValue+1);
			}
			dictionaries.set(parentValue, dict);
		}
	}

	public void setDictionaries(Vector<Dictionary> dictionaries) {
		assert(parent != null);
		this.dictionaries = dictionaries;
	}
	
	public boolean isOvert() {
		return overt;
	}

	public void setOvert(boolean overt) {
		this.overt = overt;
	}

	public String getId() {
		return id;
	}
	
	@SuppressWarnings("unchecked")
	protected void fromXML(Element xmlnode) {
		// attributes are assumed to be processed during the construction
		if (isOvert()) {
			Element e = XMLUtils.getFirstElementByTagName(xmlnode, XML_WORD_TO_OVERT_FACTOR);
			if (e != null) {
				String classname = e.getTextContent();
				try {
					Class<? extends WordToOvertFactorInterface> c = (Class<? extends WordToOvertFactorInterface>)Class.forName(classname);
					wordToOvertFactor = c.newInstance();
				} catch (ClassNotFoundException e1) {
					e1.printStackTrace();
				} catch (InstantiationException e1) {
					e1.printStackTrace();
				} catch (IllegalAccessException e1) {
					e1.printStackTrace();
				}
			}
		}
		
		String bitsStr = xmlnode.getAttribute(XML_ATTR_BITS_NAME);
		if (!bitsStr.isEmpty()) {
			bits = Byte.parseByte(bitsStr);
		}
		
		NodeList dictList = XMLUtils.getChildrenElementsByTagName(xmlnode, Dictionary.XML_ELEMENT_NAME);

		if (dictList.getLength() == 0) {
			System.err.printf("factor %s: undefined dictionary\n", name);
			setDictionary(new Dictionary(2));
		} else if (parent == null) {
			Dictionary dict = Dictionary.createDictionary((Element) dictList.item(0));
			setDictionary(dict);
			if (bits == 0) {
				// compute the number of bits required to represent the dictionary
				int len = dict.size();
				while((1 << bits) < len) ++bits;
			}
		} else {
			int longestDictionary = 0;
			int numSubdictionaries = dictList.getLength() + Dictionary.FIRST_NORMAL_VALUE;
			dictionaries = new Vector<Dictionary>(numSubdictionaries); // 0-th element is for NULL
			dictionaries.setSize(numSubdictionaries);
			setDictionary(0, new Dictionary(0));
			for(int i=0; i< dictList.getLength(); ++i) {
				Element node = (Element) dictList.item(i);
				String parentVal = node.getAttributes().getNamedItem(Dictionary.XML_ATTR_PARENT_VAL_NAME).getNodeValue();
				Dictionary dict = Dictionary.createDictionary(node);
				setDictionary(parent.getDictionary().getId(parentVal), dict);
				if (longestDictionary < dict.size()) {
					longestDictionary = dict.size();
				}
			}
			if (bits == 0 && longestDictionary > 0) {
				// compute the number of bits required to represent all the dictionaries
				while((1 << bits) < longestDictionary) ++bits;				
			}
		}
	}
	
	public byte computeBitSize() {
		byte bits = 0;
		if (parent == null) {
			int len = dictionary.size();
			while((1 << bits) < len) ++bits;			
		} else {
			int longestDictionary = 0;
			for(Dictionary dict : dictionaries) {
				if (dict != null && dict.size() > longestDictionary) {
					longestDictionary = dict.size();
				}
			}
			while((1 << bits) < longestDictionary) ++bits;
		}
		return bits;
	}
	
	protected Element createXML(Document doc) {
		Element elem = doc.createElement(XML_ELEMENT_NAME);
		elem.setAttribute(XML_ATTR_ID_NAME, id);
		elem.setAttribute(XML_ATTR_NAME_NAME, name);
		elem.setAttribute(XML_ATTR_IS_OVERT_NAME, isOvert() ? "yes" : "no");
		
		if (parent == null) {
			if (dictionary.isOpen()) {
				bits = 0;
				int len = dictionary.size();
				while((1 << bits) < len) ++bits;
			}
		} else {
			if (parent.dictionary.isOpen()) {
				int longestDictionary = 0;
				for(int i=0; i<dictionaries.size(); ++i) {
					Dictionary dictionary = dictionaries.get(i);
					if (dictionary == null) continue;
					if (longestDictionary < dictionary.size()) {
						longestDictionary = dictionary.size();
					}
				}
				bits = 0;
				if (longestDictionary > 0) {
					while((1 << bits) < longestDictionary) ++bits;				
				}
			}
		}
		elem.setAttribute(XML_ATTR_BITS_NAME, Byte.toString(bits));
		
		if (isOvert() && wordToOvertFactor != null) {
			Element e = doc.createElement(XML_WORD_TO_OVERT_FACTOR);
			e.setTextContent(wordToOvertFactor.getClass().getCanonicalName());
			elem.appendChild(e);
		}
		if (parent == null) {
			elem.appendChild(dictionary.createXML(doc));
		} else {
			elem.setAttribute(XML_ATTR_PARENT_ID_NAME, parent.getId());
			for(int i=0; i<dictionaries.size(); ++i) {
				Dictionary dictionary = dictionaries.get(i);
				if (dictionary == null) continue;
				// don't include special values into dependent vocabularies
				Element dict = dictionary.createXML(doc, false);
				dict.setAttribute(Dictionary.XML_ATTR_PARENT_VAL_NAME, parent.getDictionary().getWord(i));
				elem.appendChild(dict);
			}
		}
		return elem;
	}
	
	public WordToOvertFactorInterface getWordToOvertFactor() {
		return wordToOvertFactor;
	}

	public void setWordToOvertFactor(WordToOvertFactorInterface wordToOvertFactor) {
		this.wordToOvertFactor = wordToOvertFactor;
	}

	/**
	 * @param tuple with the primary overt factor (word) set
	 * @return the array of possible values for the current overt factor if more than one, 
	 * otherwise sets it in the tuple and returns null
	 */
	protected int[] dependentOvertFactors(FactorTuple tuple) {
		assert(isOvert());
		if (wordToOvertFactor == null) return null;
		byte mainIdx = FactorTuple.getDescription().getMainFactorIndex();
		int mainFactor = tuple.getValue(mainIdx);
		if (Dictionary.isUnk(mainFactor)) {
			// <unk> can be anything except <s>, </s>, <NULL>
			int size = dictionary.size() - Dictionary.FIRST_NORMAL_VALUE + 1;
			int[] values = new int[size];
			DictionaryIterator iter = dictionary.iterator(false);
			int i = 0;
			values[i++] = Dictionary.getUnk();
			while(iter.hasNext()) {
				values[i++] = iter.next();
			}
			return values;
		} else if (mainFactor < Dictionary.FIRST_NORMAL_VALUE) {
			int[] values = new int[1];
			values[0] = mainFactor;
			return values;
		} else {
			// normal word
			FactorDescription mainDesc = FactorTuple.getDescription().getDescription(mainIdx);
			String surfaceWord = mainDesc.getDictionary().getWord(mainFactor);
			int[] values = new int[1];
			String overtFactor = wordToOvertFactor.wordToOvertFactor(surfaceWord);
			values[0] = dictionary.getId(overtFactor);
			/*
			if (Dictionary.isUnk(values[0])) {
				wordToOvertFactor.wordToOvertFactor(surfaceWord);
			}
			*/
			return values;
		}
	}

	protected byte getBits() {
		return bits;
	}

	protected void setBits(byte bits) {
		this.bits = bits;
	}
}
